{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "5ddaea0e-ab9f-49fa-80b6-c03451d4e0aa",
   "metadata": {},
   "outputs": [],
   "source": [
    "from dotenv import load_dotenv\n",
    "from IPython.display import Markdown, display, update_display\n",
    "from openai import OpenAI\n",
    "import ollama"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "f5137285-1cb5-4af8-bb8d-4a701734a2bc",
   "metadata": {},
   "outputs": [],
   "source": [
    "MODEL_LLAMA = 'llama3.2'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "7f2eb889-b1c2-47a5-8099-e81b5e8b0eb7",
   "metadata": {},
   "outputs": [],
   "source": [
    "# set up environment\n",
    "\n",
    "load_dotenv()\n",
    "openai = OpenAI(base_url=\"http://localhost:11434/v1\", api_key=\"ollama\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "5605b2e3-90b8-46eb-95fa-1d0d849da4cd",
   "metadata": {},
   "outputs": [],
   "source": [
    "question = \"\"\"\n",
    "Please explain what this code does and why:\n",
    "yield from {book.get(\"author\") for book in books if book.get(\"author\")}\n",
    "\"\"\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "36e2c281-326f-45c2-a805-56c1490429b6",
   "metadata": {},
   "outputs": [],
   "source": [
    "system_prompt = \"You are a helpful technical tutor who answers questions about python code, software engineering, data science and LLMs\"\n",
    "user_prompt = \"Please give a detailed explanation to the following question: \"+question"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "f17a24dc-2231-4430-b4fb-502510ab5943",
   "metadata": {},
   "outputs": [],
   "source": [
    "messages = [\n",
    "    {\"role\":\"system\",\"content\": system_prompt},\n",
    "    {\"role\":\"user\", \"content\":user_prompt}\n",
    "]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "id": "ae091c92-29bc-483d-ad09-66a3226369aa",
   "metadata": {},
   "outputs": [],
   "source": [
    "stream = openai.chat.completions.create(model=MODEL_LLAMA, messages=messages,stream=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "id": "1d454d7f-f8e7-40ad-8c76-0945cd6955aa",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "**Explanation of the given code**\n",
       "\n",
       "The code snippet you provided appears to be written in Python. It utilizes the `dict` data type that includes a subset called `items()` which allows us to iterate over both the key-value pairs and the dictionary as a whole (`dict.items()` instead of just iterating) for accessing their members.\n",
       "\n",
       "Here is how you can break it down:\n",
       "\n",
       "1. `{book.get(\"author\") for book in books if book.get(\"author\")}`\n",
       "\n",
       "   This part of the code creates an iterator (`generator expression`) that produces values from each dictionary that contain `author` as a key.\n",
       "\n",
       "   Here's what's happening in more detail:\n",
       "   - `for book in books`: Iterate over each item (dictionary) in the list called `books`.\n",
       "   - `if book.get(\"author\")`: Skip to the next dictionary when its value for `\"author\"` is not defined. This prevents any potential KeyError.\n",
       "   - `book.get(\"author\")`: Return the author's name from each dictionary that has a valid \"author\".\n",
       "\n",
       "2. `yield from`\n",
       "\n",
       "   When used inside an existing generator or iterator, `yield from` will take values from another generator iterates over them one after another and yield each of the yielded values themselves.\n",
       "\n",
       "So when combining these with a dict like this:\n",
       "\n",
       "```python\n",
       "books = [\n",
       "    {\"id\": 1, \"title\": \"Python for Data Science\", \"author\": \"John Smith\"},\n",
       "    {\"id\": 2, \"title\": \"Data Analysis with Python\", \"author\": None},\n",
       "    {\"id\": 3, \"title\": \"Introduction to Machine Learning\", \"author\": \"Emily Davis\"}\n",
       "]\n",
       "\n",
       "for author in yield from {book.get(\"author\") for book in books if book.get(\"author\")}:\n",
       "    print(author)\n",
       "```\n",
       "\n",
       "It will allow us to get all the authors while skipping any that don't exist."
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "display(Markdown(stream.choices[0].message.content))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "45d1526e-f182-4432-858e-aa1cb3e9f95d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "Let's break down this line of code.\n",
       "\n",
       "**What is a ` yielding` function?**\n",
       "\n",
       "In Python, `yield` is a keyword used to define generators. A generator is similar to a list comprehension, but instead of returning a new list, it returns an iterable that generates its values on the fly when iterated. This allows you to create functions that can produce multiple values over time, without having to store them all at once in memory.\n",
       "\n",
       "**The `yield from` expression**\n",
       "\n",
       "Now, let's talk about `yield from`. It was introduced in Python 3.3 as a way to delegate iteration to another iterator. When used with the `yield` keyword, it tells Python to yield values one by one from the input sequence.\n",
       "\n",
       "In essence, `yield from` is like saying: \"instead of yielding all my values at once, use the values of this other iterable and yield each one in order\". This allows us to build complex iterators by combining smaller ones.\n",
       "\n",
       "**The code**\n",
       "\n",
       "Now that we have a basic understanding of generators and `yield`, let's get back to our original line of code:\n",
       "\n",
       "python\n",
       "yield from {book.get(\"author\") for book in books if book.get(\"author\")}\n",
       "\n",
       "\n",
       "Here, the inner dictionary comprehension is generating values. The outer expression uses `yield from` to delegate iteration to this inner iterable.\n",
       "\n",
       "Let's break down how it works:\n",
       "\n",
       "1. `{book.get(\"author\") for book in books if bookget(\"author\")}`: This is a dictionary comprehension that creates an inner iterable. It does the following:\n",
       "    - For each `book` in `books`, it calls `.get(\"author\")` and includes its value in the resulting dictionary.\n",
       "    - The `if book.get(\"author\")` part is a filter on the input data, allowing only books with authors to pass through into the inner iterable.\n",
       "\n",
       "2. However, this result of the inner comprehension is **not iterable** by itself (it returns an empty dictionary). This would cause a TypeError when trying to iterate over it using `yield from`.\n",
       "\n",
       "To fix this, we wrap the inner comprehension in parentheses, as `{}` will create an empty dictionary and return values if `.get(\"author\")` isn't None. Then the values are iterated using `yield from`, generating each value one by one.\n",
       "\n",
       "Here's how you might write this more idiomatically:\n",
       "\n",
       "python\n",
       "def get_authors():\n",
       "    for book in books:\n",
       "        yield book.get('author') or None\n",
       "\n",
       "# Usage: \n",
       "for author in get_authors():\n",
       "    print(author)\n",
       "\n",
       "\n",
       "In summary, the line `yield from {book.get(\"author\") for book in books if book.get(\"author\")}` is using generators to:\n",
       "\n",
       "- Create an inner iterable with an iterative method by calling `.get()` on objects\n",
       "- Use `yield from` to create another generator that yields these iterables' values.\n",
       "\n",
       "**Example Use Cases:**\n",
       "\n",
       "This technique can be applied when you want to generate all unique keys or elements (iterables) in multiple lists, dictionaries, or any other iterable with similar properties, while avoiding memory issues like those seen with regular for loops."
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "response = \"\"\n",
    "display_handle = display(Markdown(\"\"), display_id=True)\n",
    "for chunk in stream:\n",
    "    response += chunk.choices[0].delta.content or ''\n",
    "    response = response.replace(\"```\",\"\").replace(\"markdown\", \"\")\n",
    "    # print(chunk.choices[0].delta.content or '')\n",
    "    update_display(Markdown(response), display_id=display_handle.display_id)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "id": "fc6c3c51-01aa-4f13-a3b0-e8d7302205ff",
   "metadata": {},
   "outputs": [],
   "source": [
    "def set_user_prompt(prompt):\n",
    "    # print(\"myprompt\"+ prompt)\n",
    "    system_prompt = \"You are a helpful technical tutor who answers questions about python code, software engineering, data science and LLMs\"\n",
    "    user_prompt = prompt\n",
    "    messages = [\n",
    "        {\"role\":\"system\",\"content\": system_prompt},\n",
    "        {\"role\":\"user\", \"content\":user_prompt}\n",
    "    ]\n",
    "    return messages;"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "id": "08027166-79d8-40c3-9346-32067761ca97",
   "metadata": {},
   "outputs": [],
   "source": [
    "def call_stream_openai(user_prompt):\n",
    "    messages = set_user_prompt(user_prompt)\n",
    "    stream = openai.chat.completions.create(model=MODEL_LLAMA, messages=messages,stream=True)\n",
    "    return stream"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "id": "db21317d-b1c3-490b-8f3a-04b7a5f7a41f",
   "metadata": {},
   "outputs": [],
   "source": [
    "def my_chat(user_prompt):\n",
    "    print(\"my chat called\")\n",
    "    stream = call_stream_openai(user_prompt)\n",
    "    response = \"\"\n",
    "    display_handle = display(Markdown(\"\"), display_id=True)\n",
    "    for chunk in stream:\n",
    "        response += chunk.choices[0].delta.content or ''\n",
    "        response = response.replace(\"```\",\"\").replace(\"markdown\", \"\")\n",
    "        update_display(Markdown(response), display_id=display_handle.display_id)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 70,
   "id": "764f629c-3a10-49aa-9ec9-d273f8f93cf1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdin",
     "output_type": "stream",
     "text": [
      "Please enter your question: textarea in python input\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "my chat called\n"
     ]
    },
    {
     "data": {
      "text/markdown": [
       "**Creating a Text Area Input in Python**\n",
       "\n",
       "To create a text area input in Python, you can use the `ttk.Entry` widget from the Tkinter library. Here is an example of how to do it:\n",
       "python\n",
       "import tkinter as tk\n",
       "from tkinter import ttk\n",
       "\n",
       "# Create the main window\n",
       "root = tk.Tk()\n",
       "root.title(\"Text Area Input\")\n",
       "\n",
       "# Create a text area widget\n",
       "textarea = ttk.Entry(root, width=50, height=10)\n",
       "\n",
       "# Add a label and the input field to the window\n",
       "label = tk.Label(root, text=\"Enter some text:\")\n",
       "label.pack()\n",
       "textarea.pack()\n",
       "\n",
       "# Function to handle button click\n",
       "def submit_text():\n",
       "    text = textarea.get(\"1.0\", \"end-1c\")\n",
       "    print(text)\n",
       "\n",
       "# Create a submit button\n",
       "submit_button = tk.Button(root, text=\"Submit\", command=submit_text)\n",
       "submit_button.pack()\n",
       "\n",
       "# Start the Tkinter event loop\n",
       "root.mainloop()\n",
       "\n",
       "This code creates a simple window with a label and a text area input field. When you click the \"Submit\" button, it prints the text you entered in the text area to the console.\n",
       "\n",
       "**Example Use Cases**\n",
       "\n",
       "* Creating a simple form or survey where users can enter their responses.\n",
       "* Allowing users to input and edit data in a web-based application.\n",
       "* Capturing user input from a command-line interface (CLI) application.\n",
       "\n",
       "**Tips and Variations**\n",
       "\n",
       "* You can adjust the `width` and `height` parameters of the `ttk.Entry` widget to change the size of the text area input field.\n",
       "* You can add other widgets or features to the window, such as buttons, radio buttons, checkboxes, or a dropdown menu, by adding more instances of `tk.Label`, `tk.Button`, etc."
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "my_question = input(\"Please enter your question:\")\n",
    "my_chat(my_question)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ce3f2e8e-e722-4698-bd73-7f35dad5a728",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
